import {isObject} from "../utils.js";
import {arrayMethods} from "./array";
import Dep from "./dep.js";

// es6的类来实现的
class Observer {
  constructor(data) {
    // 对数组索引进行拦截 性能差而且直接更改索引的方式并不多
    // data.__ob__ = this
    // 当前数据对象已经被响应式处理过了 响应式的标记，如果有这个属性，代表属性已经被响应式处理过了
    Object.defineProperty(data, "__ob__", {
      // __ob__ 是一个响应式饿表示 对象数组都有
      enumerable: false, // 不可枚举
      configurable: false, // 不可删除，不可修改，不可重新定义，不可配置
      value: this,
    });
    // data.__ob__ = this; // 相当于在数据上可以获取到__ob__这个属性 指代的是Observer的实例
    // data对象或者数组
    // 为什么这里的data会有数组类型？
    if (Array.isArray(data)) { // 数组的处理逻辑 data >> ['a', 'b', 'c']
      // vue如何对数组进行处理呢？ 数组用的是重写数组的方法  函数劫持
      // 改变数组本身的方法我就可以监控到了
      data.__proto__ = arrayMethods; // 重写当前数组的原型方法 push/shift/unshift/pop
      // 每一级的对象或者数组都要处理成响应式的数据
      this.observeArray(data); // data是一个数组
      // 1、重写数组的原型方法(push,....7个)
      // 2、遍历数组，判断每一项是不是对象或者数组，如果是继续处理
    } else {
      // data是一个对象
      this.walk(data); // 可以对数据一步一步的处理
    }
  }

  observeArray(data) {
    // data数组
    // 遍历data数据，对数组中的每一项进行观测
    for (let i = 0; i < data.length; i++) {
      observe(data[i]);
    }
  }

  // data是一个对象
  walk(data) {
    // data 是一个对象
    // {name: '', age: ''}
    // 对象的键名组成的数组 ['name', 'age']
    Object.keys(data).forEach((key) => {
      // key name age
      // data: 数据对象
      // key：数据对象的key
      // data[key]：数据对象的值
      defineReactive(data, key, data[key]); // 定义响应式的数据变化
    });
  }
}

// const obj = {
//   name: "zhangsan",
//   age: 18,
//   info: {
//     age: "",
//   },
// };

// const newProxy = new Proxy(obj, {
//   get(target, key) {
//     return target[key];
//   },
//   set() {},
// });

// 编译模板的时候会获取数据
// 获取数据会触发当前数据的get方法
// get方法触发，会把当前的组件更新方法添加到当前数据的dep对象里面
// vue的更新操作是组件级别的

// vue2 的性能 递归重写get和set  proxy
// 闭包的试用场景
// get的时候，收集依赖
// 如果当前数据在组件里面使用到了，当前组件的实例/更新方法收集到dep里面的subs数组里面
// 修改数据的时候，会触发set方法，把收集到依赖，进行更新

// vue2里面，更新粒度是组件级别
// {name: {}, msg: []}
// vue2会递归把所有的数据全部处理成响应式
// vue2的响应式有什么缺点：
// Object.defineProperty只能监听单个属性。
// 1、如果数据层次过多，递归会很消耗性能【如果是一次性使用的数据，使用Object.freeze进行冻结】
// 2、初次渲染的时候，性能消耗比较大
// 3、主动把data里面所有的数据直接全部劫持
// 数组劫持的是数组方法
// 为什么数组不适用Object.defineProperty？
// 初始化的时候只劫持data已经存在的属性
// 检测不到数据key的删除

// 会把对象不管嵌套多少层，都处理成响应式的数据
// this.arr.push({ name: "23" });
// arr[1].name = "newMsg";

this.msg = {name: "23"};

function defineReactive(data, key, value) {
  // TODO:
  // 循环遍历的过程当中，判断当前对象的值是不是一个对象，如果是，递归调用observe进行响应式处理
  observe(value);

  // get的时候收集依赖
  // set的时候更新依赖

  let dep = new Dep();  // {subs: [模板的更新Watcher]}// {} // msg.dep =[watcher]  age.dep = [watcher]  // 渲染watcher中.deps [msg.dep,age.dep]
  // 收集依赖
  Object.defineProperty(data, key, {
    get() {

      if (Dep.target) {
        dep.addSub(Dep.target);
        // dep.depend();
        // 让这个dep 去收集watcher
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      observe(newValue); // 监控当前设置的值，有可能用户给了一个新值
      value = newValue;
      // 当我们更新数据后 要把当前自己对应的watcher 去重新执行以下。
      // 收集进去的依赖进行更新。
      dep.notify();
    },
  });
}

export function observe(data) {
  // 对象就是使用defineProperty 来实现响应式原理
  // 如果这个数据不是对象 或者是null 那就不用监控了
  if (!isObject(data)) {
    return;
  }
  // 对象或者数组
  // 当前数据是否已经被响应式劫持过
  // TODO: 为什么要判断__ob__属性？ 防止对象被重复观测
  if (data.__ob__) {
    return;
  }
  // if (data.__ob__ instanceof Observer) { // 防止对象被重复观测
  //   return;
  // }

  // 对数据进行defineProperty
  return new Observer(data); // 可以看到当前数据是否被观测过
}
